/*
 * Copyright (c) 2020 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.api.client.extensions.harmony.json;

import ohos.utils.zson.ZSONException;
import ohos.utils.zson.ZSONReader;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.JsonParser;
import com.google.api.client.json.JsonToken;
import com.google.api.client.util.Beta;
import com.google.api.client.util.Preconditions;

import java.io.EOFException;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

/**
 * {@link Beta} <br>
 * Low-level JSON serializer implementation based on GSON.
 *
 * <p>Implementation is not thread-safe.
 */
@Beta
class HarmonyJsonParser extends JsonParser {
    private final ZSONReader reader;
    private final HarmonyJsonFactory factory;

    private List<String> currentNameStack = new ArrayList<String>();
    Stack<String> blocks_stack = new Stack<String>();
    private JsonToken currentToken;
    private String currentText;

    HarmonyJsonParser(HarmonyJsonFactory factory, ZSONReader reader) {
        this.factory = factory;
        this.reader = reader;
    }

    @Override
    public void close() throws IOException {
        reader.close();
    }

    @Override
    public String getCurrentName() {
        return currentNameStack.isEmpty() ? null : currentNameStack.get(currentNameStack.size() - 1);
    }

    @Override
    public JsonToken getCurrentToken() {
        return currentToken;
    }

    @Override
    public JsonFactory getFactory() {
        return factory;
    }

    @Override
    public byte getByteValue() {
        checkNumber();
        return Byte.parseByte(currentText);
    }

    @Override
    public short getShortValue() {
        checkNumber();
        return Short.parseShort(currentText);
    }

    @Override
    public int getIntValue() {
        checkNumber();
        return Integer.parseInt(currentText);
    }

    @Override
    public float getFloatValue() {
        checkNumber();
        return Float.parseFloat(currentText);
    }

    @Override
    public BigInteger getBigIntegerValue() {
        checkNumber();
        return new BigInteger(currentText);
    }

    @Override
    public BigDecimal getDecimalValue() {
        checkNumber();
        return new BigDecimal(currentText);
    }

    @Override
    public double getDoubleValue() {
        checkNumber();
        return Double.parseDouble(currentText);
    }

    @Override
    public long getLongValue() {
        checkNumber();
        return Long.parseLong(currentText);
    }

    private void checkNumber() {
        Preconditions.checkArgument(
                currentToken == JsonToken.VALUE_NUMBER_INT || currentToken == JsonToken.VALUE_NUMBER_FLOAT);
    }

    @Override
    public String getText() {
        return currentText;
    }

    @Override
    public JsonToken nextToken() throws IOException {
        if (currentToken != null) {
            switch (currentToken) {
                case START_ARRAY:
                    reader.startArray();
                    currentNameStack.add(null);
                    blocks_stack.push("[");
                    break;
                case START_OBJECT:
                    reader.startObject();
                    currentNameStack.add(null);
                    blocks_stack.push("{");
                    break;
                default:
                    break;
            }
        }

        // work around bug in GSON parser that it throws an EOFException for an empty document
        ohos.utils.zson.ZSONType peek;
        try {
            peek = reader.peek();
        } catch (ZSONException e) {
            peek = ohos.utils.zson.ZSONType.UNKNOWN;
            currentText = null;
            currentToken = null;
            return null;
        }
        switch (peek) {
            case ARRAY:
                currentText = "[";
                currentToken = JsonToken.START_ARRAY;
                break;

            case OBJECT:
                currentText = "{";
                currentToken = JsonToken.START_OBJECT;
                break;

            case BOOLEAN:
                if (reader.readBoolean()) {
                    currentText = "true";
                    currentToken = JsonToken.VALUE_TRUE;
                } else {
                    currentText = "false";
                    currentToken = JsonToken.VALUE_FALSE;
                }
                break;
            case NULL:
                currentText = "null";
                currentToken = JsonToken.VALUE_NULL;
                reader.readNull();
                break;
            case STRING:
                currentText = reader.readString();
                currentToken = JsonToken.VALUE_STRING;
                break;
            case INTEGER:
                currentText = String.valueOf(reader.readInt());
                currentToken = JsonToken.VALUE_NUMBER_INT;
                break;
            case FLOAT:
                currentText = String.valueOf(reader.readFloat());
                currentToken = JsonToken.VALUE_NUMBER_FLOAT;
                break;
            case NAME:
                currentText = reader.readName();
                currentToken = JsonToken.FIELD_NAME;
                currentNameStack.set(currentNameStack.size() - 1, currentText);
                break;

            case UNKNOWN:
                if (!blocks_stack.empty()) {
                    String type = blocks_stack.pop();
                    if (type.equals("[")) {
                        currentText = "]";
                        currentToken = JsonToken.END_ARRAY;
                        reader.hasNext();
                        reader.endArray();
                    } else {
                        currentText = "}";
                        currentToken = JsonToken.END_OBJECT;
                        reader.hasNext();
                        reader.endObject();
                    }
                    currentNameStack.remove(currentNameStack.size() - 1);
                }
                break;

            default:
                currentText = null;
                currentToken = null;
                break;
        }
        return currentToken;
    }

    @Override
    public JsonParser skipChildren() throws IOException {
        if (currentToken != null) {
            switch (currentToken) {
                case START_ARRAY:
                    reader.skipValue();
                    currentText = "]";
                    currentToken = JsonToken.END_ARRAY;
                    break;
                case START_OBJECT:
                    reader.skipValue();
                    currentText = "}";
                    currentToken = JsonToken.END_OBJECT;
                    break;
                default:
                    break;
            }
        }
        return this;
    }
}
